經過上篇苦戰之後,終於進到最後講解JTAG訊號控制的部分!
本篇可能會有點長和無聊!
筆者盡量以程式碼配合範例的方式說明!
在開始介紹JTAG Operation之前,我們先來複習一下「Day 24: 您不可不知的FT2232H (2/3) - MPSSE Command Processor」中,"2. JTAG Control"的其中幾個會用到的Command,主要是控制TDI、TDO和TMS,
並看看實際程式碼!!
以目前實作來說,主要是使用以下兩種Command,用來做TDI傳輸的部分
前者主要在TDI資料長度大於1個Byte的時候,以Bytes的方式送出;
後者主要用在資料長度小於1個Byte的時候,以Bits的方式送出
來看看實際程式實作的部分,請參考(src/jtag/drivers/mpsse.c):
void mpsse_clock_data(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
unsigned in_offset, unsigned length, uint8_t mode)
{
/* TODO: Fix MSB first modes */
DEBUG_IO("%s%s %d bits", in ? "in" : "", out ? "out" : "", length);
if (ctx->retval != ERROR_OK) {
DEBUG_IO("Ignoring command due to previous error");
return;
}
/* TODO: On H chips, use command 0x8E/0x8F if in and out are both 0 */
if (out || (!out && !in))
mode |= 0x10;
if (in)
mode |= 0x20;
while (length > 0) {
/* Guarantee buffer space enough for a minimum size transfer */
if (buffer_write_space(ctx) + (length < 8) < (out || (!out && !in) ? 4 : 3)
|| (in && buffer_read_space(ctx) < 1))
ctx->retval = mpsse_flush(ctx);
if (length < 8) {
/* Transfer remaining bits in bit mode */
buffer_write_byte(ctx, 0x02 | mode);
buffer_write_byte(ctx, length - 1);
if (out)
out_offset += buffer_write(ctx, out, out_offset, length);
if (in)
in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
if (!out && !in)
buffer_write_byte(ctx, 0x00);
length = 0;
} else {
/* Byte transfer */
unsigned this_bytes = length / 8;
/* MPSSE command limit */
if (this_bytes > 65536)
this_bytes = 65536;
/* Buffer space limit. We already made sure there's space for the minimum
* transfer. */
if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
this_bytes = buffer_write_space(ctx) - 3;
if (in && this_bytes > buffer_read_space(ctx))
this_bytes = buffer_read_space(ctx);
if (this_bytes > 0) {
buffer_write_byte(ctx, mode);
buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
buffer_write_byte(ctx, (this_bytes - 1) >> 8);
if (out)
out_offset += buffer_write(ctx,
out,
out_offset,
this_bytes * 8);
if (in)
in_offset += buffer_add_read(ctx,
in,
in_offset,
this_bytes * 8,
0);
if (!out && !in)
for (unsigned n = 0; n < this_bytes; n++)
buffer_write_byte(ctx, 0x00);
length -= this_bytes * 8;
}
}
}
}
好,結束XDD 太長了.....!
一樣,我們拆成N個部份來解釋!
這邊先提醒一下,參數中的mode如果沒有特別說明,一律用以下JTAG_MODE
的設定!
// src/jtag/drivers/mpsse.h
#define POS_EDGE_OUT 0x00
#define POS_EDGE_IN 0x00
#define LSB_FIRST 0x08
// src/jtag/drivers/ftdi.c
#define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT)
首先是判斷欲執行的動作是否包含寫入TDI,或是讀取TDO:
if (out || (!out && !in))
mode |= 0x10;
if (in)
mode |= 0x20;
並將對應的Bit 4/5拉起來,還記的以下編碼的規則嗎XD!?
再來是迴圈處理的部分,簡單的來說就是把Data拆成Bytes和Bits兩部分來處理!
首先是Bits傳輸的部分:
/* Transfer remaining bits in bit mode */
buffer_write_byte(ctx, 0x02 | mode);
buffer_write_byte(ctx, length - 1);
if (out)
out_offset += buffer_write(ctx, out, out_offset, length);
if (in)
in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
if (!out && !in)
buffer_write_byte(ctx, 0x00);
length = 0;
這邊很簡單,基本上就是用
再來是Bytes傳輸的部分,首先是檢查一下資料的長度不能大於65536個Bytes(傳輸上限):
/* Byte transfer */
unsigned this_bytes = length / 8;
/* MPSSE command limit */
if (this_bytes > 65536)
this_bytes = 65536;
/* Buffer space limit. We already made sure there's space for the minimum
* transfer. */
if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
this_bytes = buffer_write_space(ctx) - 3;
if (in && this_bytes > buffer_read_space(ctx))
this_bytes = buffer_read_space(ctx);
接著是傳輸Bytes:
if (this_bytes > 0) {
buffer_write_byte(ctx, mode);
buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
buffer_write_byte(ctx, (this_bytes - 1) >> 8);
if (out)
out_offset += buffer_write(ctx,
out,
out_offset,
this_bytes * 8);
if (in)
in_offset += buffer_add_read(ctx,
in,
in_offset,
this_bytes * 8,
0);
if (!out && !in)
for (unsigned n = 0; n < this_bytes; n++)
buffer_write_byte(ctx, 0x00);
length -= this_bytes * 8;
依照不同狀況,分別使用對應的MPSSE Command:
這邊的TMS Opertaion主要是用在JTAG TAP State Machine的控制上,讓JTAG進入到指定的State中!
一樣先上程式碼,請參考(src/jtag/drivers/mpsse.c):
void mpsse_clock_tms_cs(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
unsigned in_offset, unsigned length, bool tdi, uint8_t mode)
{
DEBUG_IO("%sout %d bits, tdi=%d", in ? "in" : "", length, tdi);
assert(out);
if (ctx->retval != ERROR_OK) {
DEBUG_IO("Ignoring command due to previous error");
return;
}
mode |= 0x42;
if (in)
mode |= 0x20;
while (length > 0) {
/* Guarantee buffer space enough for a minimum size transfer */
if (buffer_write_space(ctx) < 3 || (in && buffer_read_space(ctx) < 1))
ctx->retval = mpsse_flush(ctx);
/* Byte transfer */
unsigned this_bits = length;
/* MPSSE command limit */
/* NOTE: there's a report of an FT2232 bug in this area, where shifting
* exactly 7 bits can make problems with TMS signaling for the last
* clock cycle:
*
* http://developer.intra2net.com/mailarchive/html/libftdi/2009/msg00292.html
*/
if (this_bits > 7)
this_bits = 7;
if (this_bits > 0) {
buffer_write_byte(ctx, mode);
buffer_write_byte(ctx, this_bits - 1);
uint8_t data = 0;
/* TODO: Fix MSB first, if allowed in MPSSE */
bit_copy(&data, 0, out, out_offset, this_bits);
out_offset += this_bits;
buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
if (in)
in_offset += buffer_add_read(ctx,
in,
in_offset,
this_bits,
8 - this_bits);
length -= this_bits;
}
}
}
首先將對應Write TMS的Bit拉起來,並判斷是否要同時讀取TDO
mode |= 0x42;
if (in)
mode |= 0x20;
接著以7個Bits為一單位,依序將TMS送出:
if (this_bits > 7)
this_bits = 7;
if (this_bits > 0) {
buffer_write_byte(ctx, mode);
buffer_write_byte(ctx, this_bits - 1);
uint8_t data = 0;
/* TODO: Fix MSB first, if allowed in MPSSE */
bit_copy(&data, 0, out, out_offset, this_bits);
out_offset += this_bits;
buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
if (in)
in_offset += buffer_add_read(ctx,
in,
in_offset,
this_bits,
8 - this_bits);
length -= this_bits;
}
送出的方法也很簡單啦,主要是用以下Command:
別忘記這邊有個小小"tricky"的地方!
就是Byte1的Bit 7在TMS開始傳輸前,「會被放到TDI上」
bool tdi
就是用來判斷TDI是否需要特別寫出"1"!
看完上面底層MPSSE的操作後,接下來我們來研究下JTAG Command是如何被執行的!
一般在上層的應用當中,會先將所需要讀/寫資料的動作轉成對應的JTAG Command後,
推入OpenOCD內部的JTAG Queue中先存放,比方說下面這個例子:
EX: 讀取IDCODE
static int scan_idcode(struct target *target)
{
struct scan_field field;
uint8_t in_value[4];
jtag_add_ir_scan(target->tap, &select_idcode, TAP_IDLE);
field.num_bits = 32;
field.out_value = NULL;
field.in_value = in_value;
jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
int retval = jtag_execute_queue();
if (retval != ERROR_OK) {
LOG_ERROR("failed jtag scan: %d", retval);
return retval;
}
uint32_t in = buf_get_u32(field.in_value, 0, 32);
LOG_DEBUG("IDCODE: 0x0 -> 0x%x", in);
retrun ERROR_OK
}
從這個例子當中,可以看到讀取IDCODE這件事可以分成三個步驟:
接下來重點來啦,讓我們先看看JTAG中TAP的State Machine(狀態機):
---引用自OpenOCD Developer's Guide - OpenOCD JTAG Primer
這邊先說一下,不曉得為啥外部連結圖片的功能突然顯示不出來,只好直接上傳到這邊
讓我們將上面三個步驟拆成N個詳細的JTAG操作(假設狀態機一開始就在Run-Test/Idle中,IR指令的長度為5-bits):
不過這個步驟是有問題的!!!
請看Step 2地方! 這邊應該只需要敲入4-bits,剩下的一個bits,應該留到Step 3,
Path Move敲入TSM = 1的時候,同時把TDI = MSB的0x01 = 0x0敲入!
同理,Step 7的地方,這邊不需要讀32-bits,應該是讀31-bits才對!
整個詳細的步驟變成下圖:
以上概念講完了,回到程式碼中,讓我們看看jtag_execute_queue()中做了什麼事情,
請參考(src/jtag/core.c、src/jtag/drivers/driver.c)
int jtag_execute_queue(void)
{
jtag_execute_queue_noclear();
return jtag_error_clear();
}
void jtag_execute_queue_noclear(void)
{
jtag_flush_queue_count++;
jtag_set_error(interface_jtag_execute_queue());
....後面不重要
}
int interface_jtag_execute_queue(void)
{
static int reentry;
assert(reentry == 0);
reentry++;
int retval = default_interface_jtag_execute_queue();
....後面不重要
}
int default_interface_jtag_execute_queue(void)
{
if (NULL == jtag) {
LOG_ERROR("No JTAG interface configured yet. "
"Issue 'init' command in startup scripts "
"before communicating with targets.");
return ERROR_FAIL;
}
int result = jtag->execute_queue();
....後面不重要
}
走到這裡,終於看到jtag->execute_queue()
,也就是呼叫ftdi_execute_queue()
來處理這些JTAG Commands!
基本上Execute Queue很簡單,就是把JTAG Command Queue中的Command一個個讀出來,
然後判斷要執行的動作,再呼叫對應處理的函式去處理!
ftdi_execute_queue()內容如下,請參考(src/jtag/drivers/ftdi.c):
static int ftdi_execute_queue(void)
{
/* blink, if the current layout has that feature */
struct signal *led = find_signal_by_name("LED");
if (led)
ftdi_set_signal(led, '1'); ///譯註: 閃爍LED,基本上就是把那根訊號的Value從0變1或是1變0
for (struct jtag_command *cmd = jtag_command_queue; cmd; cmd = cmd->next) {
/* fill the write buffer with the desired command */
ftdi_execute_command(cmd); ///譯註: 一個個執行JTAG Command
}
if (led)
ftdi_set_signal(led, '0');
int retval = mpsse_flush(mpsse_ctx);
if (retval != ERROR_OK)
LOG_ERROR("error while flushing MPSSE queue: %d", retval);
return retval;
}
然後在ftdi_execute_command()中就會呼叫對應的處理函式,
請參考(src/jtag/drivers/ftdi.c):
static void ftdi_execute_command(struct jtag_command *cmd)
{
switch (cmd->type) {
case JTAG_RESET:
ftdi_execute_reset(cmd);
break;
case JTAG_RUNTEST:
ftdi_execute_runtest(cmd);
break;
case JTAG_TLR_RESET:
ftdi_execute_statemove(cmd);
break;
case JTAG_PATHMOVE:
ftdi_execute_pathmove(cmd);
break;
case JTAG_SCAN:
ftdi_execute_scan(cmd); ///譯註: IR/DR Scan處理
break;
case JTAG_SLEEP:
ftdi_execute_sleep(cmd);
break;
case JTAG_STABLECLOCKS:
ftdi_execute_stableclocks(cmd);
break;
case JTAG_TMS:
ftdi_execute_tms(cmd);
break;
default:
LOG_ERROR("BUG: unknown JTAG command type encountered: %d", cmd->type);
break;
}
}
基本上接下來的章節只會提到ftdi_execute_scan(),其餘有興趣的讀者,
可以自行研究看看!其實是筆者沒時間去研究其他的操作 囧rz
先來個程式碼看看,請參考(src/jtag/drivers/ftdi.c):
static void ftdi_execute_scan(struct jtag_command *cmd)
{
DEBUG_JTAG_IO("%s type:%d", cmd->cmd.scan->ir_scan ? "IRSCAN" : "DRSCAN",
jtag_scan_type(cmd->cmd.scan));
/* Make sure there are no trailing fields with num_bits == 0, or the logic below will fail. */
while (cmd->cmd.scan->num_fields > 0
&& cmd->cmd.scan->fields[cmd->cmd.scan->num_fields - 1].num_bits == 0) {
cmd->cmd.scan->num_fields--;
LOG_DEBUG("discarding trailing empty field");
}
if (cmd->cmd.scan->num_fields == 0) {
LOG_DEBUG("empty scan, doing nothing");
return;
}
if (cmd->cmd.scan->ir_scan) {
if (tap_get_state() != TAP_IRSHIFT)
move_to_state(TAP_IRSHIFT);
} else {
if (tap_get_state() != TAP_DRSHIFT)
move_to_state(TAP_DRSHIFT);
}
ftdi_end_state(cmd->cmd.scan->end_state);
struct scan_field *field = cmd->cmd.scan->fields;
unsigned scan_size = 0;
for (int i = 0; i < cmd->cmd.scan->num_fields; i++, field++) {
scan_size += field->num_bits;
DEBUG_JTAG_IO("%s%s field %d/%d %d bits",
field->in_value ? "in" : "",
field->out_value ? "out" : "",
i,
cmd->cmd.scan->num_fields,
field->num_bits);
if (i == cmd->cmd.scan->num_fields - 1 && tap_get_state() != tap_get_end_state()) {
/* Last field, and we're leaving IRSHIFT/DRSHIFT. Clock last bit during tap
* movement. This last field can't have length zero, it was checked above. */
mpsse_clock_data(mpsse_ctx,
field->out_value,
0,
field->in_value,
0,
field->num_bits - 1,
ftdi_jtag_mode);
uint8_t last_bit = 0;
if (field->out_value)
bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
uint8_t tms_bits = 0x01;
mpsse_clock_tms_cs(mpsse_ctx,
&tms_bits,
0,
field->in_value,
field->num_bits - 1,
1,
last_bit,
ftdi_jtag_mode);
tap_set_state(tap_state_transition(tap_get_state(), 1));
mpsse_clock_tms_cs_out(mpsse_ctx,
&tms_bits,
1,
1,
last_bit,
ftdi_jtag_mode);
tap_set_state(tap_state_transition(tap_get_state(), 0));
} else
mpsse_clock_data(mpsse_ctx,
field->out_value,
0,
field->in_value,
0,
field->num_bits,
ftdi_jtag_mode);
}
if (tap_get_state() != tap_get_end_state())
move_to_state(tap_get_end_state());
DEBUG_JTAG_IO("%s scan, %i bits, end in %s",
(cmd->cmd.scan->ir_scan) ? "IR" : "DR", scan_size,
tap_state_name(tap_get_end_state()));
}
落落長! 沒關係!
我們就用上面章節介紹到讀取IDCODE為例子,來分析一下程式碼的部分,
並對照Log,看看實際執行的結果!
讓我們開始吧!!
首先一開始會判斷目前的Scan種類,區分成IR-Scan和DR-Scan:
if (cmd->cmd.scan->ir_scan) {
if (tap_get_state() != TAP_IRSHIFT)
move_to_state(TAP_IRSHIFT);
} else {
if (tap_get_state() != TAP_DRSHIFT)
move_to_state(TAP_DRSHIFT);
}
以IDCODE的例子來說,第一個Scan是IR-Scan,也就是執行move_to_state(TAP_IRSHIFT);
,將目前的狀態機從"RUN/IDLE"移動到"IRSHIFT",簡單的來說就是對TDI依序寫入1(LSB) -> 1 -> 0 -> 0(MSB)!
總共長度為4-bits、資料轉成Byte為0x3(0011)!
讓我們來瞧瞧實際執行的情況!
Debug: 40752 821 ftdi.c:265 move_to_state(): start=RUN/IDLE goal=IRSHIFT
Debug: 40753 821 ftdi.c:269 move_to_state(): tap_set_state(DRSELECT)
Debug: 40754 821 ftdi.c:269 move_to_state(): tap_set_state(IRSELECT)
Debug: 40755 821 ftdi.c:269 move_to_state(): tap_set_state(IRCAPTURE)
Debug: 40756 821 ftdi.c:269 move_to_state(): tap_set_state(IRSHIFT)
Debug: 40757 821 mpsse.c:573 mpsse_clock_tms_cs(): out 4 bits, tdi=0
Debug: 40758 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40759 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40760 821 mpsse.c:455 buffer_write_byte(): 03
你看看! 你看看! 就是這麼簡單!!!!
既然到IRSHIFT,再來就是把IDCODE(0x1)給輸入TDI中:
mpsse_clock_data(mpsse_ctx,
field->out_value,
0,
field->in_value,
0,
field->num_bits - 1,
ftdi_jtag_mode);
注意IR的長度雖然為5-bits,不過這邊只需要將前4-bit寫入就行了!
以下是實際執行的情況:
Debug: 40762 821 mpsse.c:497 mpsse_clock_data(): out 4 bits
Debug: 40763 821 mpsse.c:455 buffer_write_byte(): 1b
Debug: 40764 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40765 821 mpsse.c:463 buffer_write(): 4 bits
再來就是將IR最後的MSB敲出,並在TMS上輸入1,將狀態機從IRSHITF離開,進入到IREXIT1中:
uint8_t last_bit = 0;
if (field->out_value)
bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
uint8_t tms_bits = 0x01;
mpsse_clock_tms_cs(mpsse_ctx,
&tms_bits,
0,
field->in_value,
field->num_bits - 1,
1,
last_bit,
ftdi_jtag_mode);
tap_set_state(tap_state_transition(tap_get_state(), 1));
來看實際成果:
Debug: 40766 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40767 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40768 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40769 821 mpsse.c:455 buffer_write_byte(): 01
Debug: 40770 821 ftdi.c:494 ftdi_execute_scan(): tap_set_state(IREXIT1)
完美!
接下來就是從IREXIT1,移動到IRPAUSE:
mpsse_clock_tms_cs_out(mpsse_ctx,
&tms_bits,
1,
1,
last_bit,
ftdi_jtag_mode);
tap_set_state(tap_state_transition(tap_get_state(), 0));
Log:
Debug: 40771 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40772 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40773 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40774 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40775 822 ftdi.c:501 ftdi_execute_scan(): tap_set_state(IRPAUSE)
最後,當然要從目前的"IRPAUSE"回到"RUN/IDLE"的狀態啦!
if (tap_get_state() != tap_get_end_state())
move_to_state(tap_get_end_state());
Log:
Debug: 40776 822 ftdi.c:265 move_to_state(): start=IRPAUSE goal=RUN/IDLE
Debug: 40777 822 ftdi.c:269 move_to_state(): tap_set_state(IREXIT2)
Debug: 40778 822 ftdi.c:269 move_to_state(): tap_set_state(IRUPDATE)
Debug: 40779 822 ftdi.c:269 move_to_state(): tap_set_state(RUN/IDLE)
Debug: 40780 822 mpsse.c:573 mpsse_clock_tms_cs(): out 3 bits, tdi=0
Debug: 40781 822 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40782 822 mpsse.c:455 buffer_write_byte(): 02
Debug: 40783 822 mpsse.c:455 buffer_write_byte(): 03
以上就是簡單的IR Scan分析!
DR Scan也是類似的方式,就不多做說明! 篇幅太長,打字很累!
痾! 花了這麼長的篇幅,終於將JTAG Command轉MPSSE Command的部分剖析完畢!完整的 剖析了整個FT2232H底層的運作和實際OpenOCD5中MPSSE程式碼的部分!
如果對上述內容還是有不清楚的地方,建議把JTAG TAP State Machine印出來,
然後用紙筆來模擬訊號的進出,相信對整個流程會有更深入的了解!!!
真是一場鏖戰,打完這篇大概花了我半條命......!